﻿/** PROBLEM CLASS
 * Author: Preeti Francis
 * User ID : pxfranci@scotty.cs.wichita.edu
 * WSU ID: S578F746
 * Course: CS771 
 * Sem: S2014
 * 
 * Description of Class : The Problem class is a static class that contains utility methods used by the A* algorithm program logic. 
 * GetChildNodes() method : This method returns all the possible child nodes for a given parent state.
 * ManhattanCost() method : This method calculates the manhattan cost of a given state.
 * IsGoalState() method : Compares the current state and goal state check if goal state is reached.
 * **/
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using _8PiecePuzzle.Enum;
using _8PiecePuzzle.DataStructures;

namespace _8PiecePuzzle.ProgramLogic
{
    public static class Problem
    {
        //Public method : Checks if the state is a goal state by comparing their hash values (hash values used for optimization)
        public static bool IsGoalState(string currentStateHash, string goalStateHash)
        {
            return currentStateHash.Trim() == goalStateHash.Trim();
        }

        //Public method : Gets all possible child nodes for a given parent node
         public static List<Node> GetChildNodes(Node parentNode, int[][] goalState)
        {
            List<Node> childNodes = new List<Node>();
            
            int[][] parentState = parentNode.State;
            
            var pathCost = PathCost(parentNode.PathCost);

            var childActionResultRight = GetChildStateResult(parentState, Enum.Action.Right);
            if (childActionResultRight.IsLegal)
            {                
                Node childNode = new Node
                {
                    State = childActionResultRight.State,
                    Action = Enum.Action.Right,
                    Parent = parentNode,
                    PathCost = pathCost,
                    TotalCost = pathCost + ManhattanCost(childActionResultRight.State, goalState)
                };
                
                childNodes.Add(childNode);
            }

            var childActionResultLeft = GetChildStateResult(parentState, Enum.Action.Left);
            if (childActionResultLeft.IsLegal)
            {
                Node childNode = new Node
                {
                    State = childActionResultLeft.State,
                    Action = Enum.Action.Left,
                    Parent = parentNode,
                    PathCost = pathCost,
                    TotalCost = pathCost + ManhattanCost(childActionResultLeft.State, goalState)
                };

                childNodes.Add(childNode);
            }
            
            var childActionResultUp = GetChildStateResult(parentState, Enum.Action.Up);
            if (childActionResultUp.IsLegal)
            {
                Node childNode = new Node
                {
                    State = childActionResultUp.State,
                    Action = Enum.Action.Up,
                    Parent = parentNode,
                    PathCost = pathCost,
                    TotalCost = pathCost + ManhattanCost (childActionResultUp.State,goalState)
                };

                childNodes.Add(childNode);
            }
  
            var childActionResultDown = GetChildStateResult(parentState, Enum.Action.Down);
            if (childActionResultDown.IsLegal)
            {
                Node childNode = new Node
                {
                    State = childActionResultDown.State,
                    Action = Enum.Action.Down,
                    Parent = parentNode,
                    PathCost = pathCost,
                    TotalCost = pathCost + ManhattanCost(childActionResultDown.State,goalState)
                };

                childNodes.Add(childNode);
            }
            return childNodes;            
            
        }

        //Public method : Calculates the Mannhattan cost of a given state with respect to the goal state.
        public static int ManhattanCost(int[][] currentState, int[][] goalState)
        {
            int n = currentState.Length;

            int h = 0;

            for (int i = 0; i < n; i++)
            {
                for (int j = 0; j < n; j++)
                {
                    int[,] coordinatesOfGoal = GetCoordinate(currentState[i][j], goalState);

                    int differenceX = coordinatesOfGoal[0, 0] - i;
                    int differenceY = coordinatesOfGoal[0, 1] - j;

                    h = h + Math.Abs(differenceX) + Math.Abs(differenceY);
                }
            }

            return h;
        }

        //Internal method : Gets location coordinates of a number in a given array
        private static int[,] GetCoordinate(int number,int[][] state)
        {
            int[,] coordinate = new int[1,2];

            var n = state.Length;

            for (int i = 0; i < n; i++)
			{
                for (int j = 0; j < n; j++)
                {
                    if (number == state[i][j])
                    {
                        coordinate[0,0] = i;
                        coordinate[0,1] = j;
                        return coordinate;
                    }
                }                
            }
            return coordinate;
        }

        //Internal method : Returns the path cost g(n) which is 1 for every child
        private static int PathCost(int parentPathCost)
        {
            return parentPathCost + 1;
        }

        //Internal method : Gets the child state for an action on the parent state
        private static ActionResult GetChildStateResult(int[][] parentState, _8PiecePuzzle.Enum.Action action)
        {
            ActionResult actionResult = new ActionResult();
                        
            switch (action)
            {
                case _8PiecePuzzle.Enum.Action.Right:
                    actionResult = ApplyRightAction(parentState);
                    break;
                case _8PiecePuzzle.Enum.Action.Left:
                    actionResult = ApplyLeftAction(parentState);
                    break;
                case _8PiecePuzzle.Enum.Action.Up:
                    actionResult = ApplyUpAction(parentState);
                    break;
                case _8PiecePuzzle.Enum.Action.Down:
                    actionResult = ApplyDownAction(parentState);
                    break;
                default:
                    actionResult.IsLegal = false;
                    break;
            }

            return actionResult;
        }

        //Internal method : Applies Right action
        private static ActionResult ApplyRightAction(int[][] state)
        {
            ActionResult rightActionResult = new ActionResult();

            int maxYCoordinate = state.Length - 1;

            //Get coordinate of 0
            int[,] coordinate = GetCoordinate(0, state);
            var i = coordinate[0,0];
            var j = coordinate[0, 1];

            //Check if right action is legal by seeing if the coordinates are within bounds
            var yCoordinate = j + 1;

            if (yCoordinate > maxYCoordinate)
            {
                rightActionResult.IsLegal = false;
            
                return rightActionResult;
            }
            int[,] newCoordinate = new int[1,2]; 
            newCoordinate[0,0] = i;
            newCoordinate[0, 1] = yCoordinate;

            //Apply right action by moving 0 to the new coordinates
            rightActionResult.State = SwapPieceOfThePuzzleWithBlank(state, coordinate, newCoordinate);
            rightActionResult.IsLegal = true;

            return rightActionResult;
        }

        //Internal method : Applies Left Action
        private static ActionResult ApplyLeftAction(int[][] state)
        {
            ActionResult leftActionResult = new ActionResult();

            int minYCoordinate = 0;

            //Get coordinate of 0
            int[,] coordinate = GetCoordinate(0, state);
            var i = coordinate[0, 0];
            var j = coordinate[0, 1];

            //Check if left action is legal by seeing if the coordinates are within bounds
            var yCoordinate = j - 1;

            if (yCoordinate < minYCoordinate)
            {
                leftActionResult.IsLegal = false;

                return leftActionResult;
            }

            int[,] newCoordinate = new int[1, 2];
            newCoordinate[0, 0] = i;
            newCoordinate[0, 1] = yCoordinate;

            //Apply left action by moving 0 to the new coordinates
            leftActionResult.State = SwapPieceOfThePuzzleWithBlank(state, coordinate, newCoordinate);
            leftActionResult.IsLegal = true;

            return leftActionResult;
        }

        //Internal method : Applies Down Action
        private static ActionResult ApplyDownAction(int[][] state)
        {
            ActionResult downActionResult = new ActionResult();

            int maxXCoordinate = state.Length - 1;

            //Get coordinate of 0
            int[,] coordinate = GetCoordinate(0, state);
            var i = coordinate[0, 0];
            var j = coordinate[0, 1];

            //Check if down action is legal by seeing if the coordinates are within bounds
            var xCoordinate = i + 1;

            if (xCoordinate > maxXCoordinate)
            {
                downActionResult.IsLegal = false;

                return downActionResult;
            }
            int[,] newCoordinate = new int[1, 2];
            newCoordinate[0, 0] = xCoordinate;
            newCoordinate[0, 1] = j;

            //Apply down action by moving 0 to the new coordinates
            downActionResult.State = SwapPieceOfThePuzzleWithBlank(state, coordinate, newCoordinate);
            downActionResult.IsLegal = true;

            return downActionResult;
        }

        //Internal method : Applies Up Action
        private static ActionResult ApplyUpAction(int[][] state)
        {
            ActionResult upActionResult = new ActionResult();

            int minXCoordinate = 0;

            //Get coordinate of 0
            int[,] coordinate = GetCoordinate(0, state);
            var i = coordinate[0, 0];
            var j = coordinate[0, 1];

            //Check if up action is legal by seeing if the coordinates are within bounds
            var xCoordinate = i - 1;

            if (xCoordinate < minXCoordinate)
            {
                upActionResult.IsLegal = false;

                return upActionResult;
            }

            int[,] newCoordinate = new int[1, 2];
            newCoordinate[0, 0] = xCoordinate;
            newCoordinate[0, 1] = j;

            //Apply Up action by moving 0 to the new coordinates
            upActionResult.State = SwapPieceOfThePuzzleWithBlank(state, coordinate, newCoordinate);
            upActionResult.IsLegal = true;

            return upActionResult;
        }

        //Internal method : Swaps the 0 from old coordinates to the new coordinates and returns the new child state.
        private static int[][] SwapPieceOfThePuzzleWithBlank(int[][] state, int[,] oldCoordinates, int[,] newCoordinates)
        {
            int[][] childState = new int[3][]
            {          
                new int[3],
                new int[3],
                new int[3]
            };

            childState = CopyToBeSwapped(state, childState);
            
            int swappedNumber = state[newCoordinates[0, 0]][newCoordinates[0, 1]];

            childState[newCoordinates[0, 0]][newCoordinates[0, 1]] = 0;

            childState[oldCoordinates[0, 0]][oldCoordinates[0, 1]] = swappedNumber;

            return childState;
 
        }

        //Internal method : Copies the old state to a newly initalized array of child state in order to modify
        private static int[][] CopyToBeSwapped(int[][] state,int[][] childState)
        {
            var n = state.Length;

            for (int i = 0; i < n; i++)
            {
                for (int j = 0; j < n; j++)
                {
                    childState[i][j] = state[i][j];
                }
            }
            return childState;
        }

                
    }
}
